#!/bin/bash
#./check_kabi.sh -p Kabi.path_* -k Module.kabi_*

GENKSYMS=./scripts/genksyms/genksyms
KABI_TMP_DIR=./kabiTempDir
KABI_SYMS=./Kabi.syms
MODULE_TMP_KABI=$KABI_TMP_DIR/Module.kabi_list
cpus=`cat /proc/cpuinfo | grep processor | wc -l`
ARCH=`arch`

if (($cpus == 0)); then
    cpus=1
    echo "warning: Unable to get the number of CPUs"
fi

#处理参数
fp_flags=0
init_flag=0
clean_flag=0
help_flag=0
while getopts "c:fhik:p:r" arg 
do
    case $arg in
    k)
        #校验kabi列表
        Module_kabi_f=$OPTARG
        ;;
    p)
        #kabi路径文件列表
        Kabi_path_f=$OPTARG
        ;;
    c)
        #指定使用的配置文件
        kernel_config=$OPTARG
        ;;
    f)
        #生成kabi路径文件列表
        fp_flags=1
        ;;
    i)
        #进行初始化
        init_flag=1
        ;;
    r)
        #清理环境
        clean_flag=1
        ;;
    h)
        #帮助信息
        help_flag=1
    esac
done

#帮助信息
function help_info()
{
    echo -e "\t-c []\t指定初始化内核环境使用的配置文件"
    echo -e "\t-f\t查找kabi所在文件，并写入文件"
    echo -e "\t-h\t显示帮助信息"
    echo -e "\t-i\t初始化内核环境"
    echo -e "\t-k []\t指定要校验的kabi白名单"
    echo -e "\t-p []\t指定kabi白名单对应的路径文件"
    echo -e "\t-r\t清理环境"
}

#预处理代码
function pre_c_s()
{
    kabi_files_c_i=`grep "\.c" $Kabi_path_f | sed 's/\.c$/\.i/'`
    kabi_files_S_s=`grep "\.S" $Kabi_path_f | sed 's/\.S$/\.s/'`
    kabi_files_i=`sed -e 's/\.S$/\.s\.i/' -e 's/\.c$/\.i/' $Kabi_path_f`
    kabi_files_num=`wc -l < $Kabi_path_f`
    kabi_file_array=(${kabi_files_i})

    echo "~~~ 开始预处理C文件"
    #预处理c代码文件
    if [ "${kabi_files_c_i}" == "" ]; then
        echo "error: format error of ${Kabi_path_f}"
        exit 1
    fi
    make -j${cpus} CPP="gcc -E -D__GENKSYMS__" $kabi_files_c_i >> /dev/null

    echo "~~~ 开始预处理汇编文件"
    #预处理汇编代码
    if [ "$kabi_files_S_s" == "" ]; then
        return 0
    fi
    kabi_files_S_si=`grep "\.S" $Kabi_path_f | sed 's/\.S$/\.s\.i/'`
    kabi_files_S_sc=`grep "\.S" $Kabi_path_f | sed 's/\.S$/\.s\.c/'`

    make -j${cpus} CPP="gcc -E -D__ASSEMBLY__ -Wa,-gdwarf-2" $kabi_files_S_s >> /dev/null
    for i in ${kabi_files_S_s[@]}
    do
        (echo "#include <linux/kernel.h>" ; \
        echo "#include <asm/asm-prototypes.h>" ; \
        cat $i | \
        grep "\<___EXPORT_SYMBOL\>" | \
        sed 's/.*___EXPORT_SYMBOL[[:space:]]*\([a-zA-Z0-9_]*\)[[:space:]]*,.*/EXPORT_SYMBOL(\1);/' ) > $i.c
    done
    make -j${cpus} CPP="gcc -E -D__GENKSYMS__" ${kabi_files_S_si} >> /dev/null
}

#处理kabi_files_i数组中的一段文件
# $1: 起始下标
# $2: 间隔
# $3: 循环次数
function build_cs()
{
    for ((ci=$1,ct=0;ct<$3;ci+=$2,ct++));
    do
        ( $GENKSYMS < ${kabi_file_array[$ci]} ) >> $KABI_TMP_DIR/kabi.$!
    done
}

#新分配器，间隔处理数据
# $1: 总负载数量
# $2: 工作函数
function thread_alloc()
{
    nums=$1
    let avg2=nums/cpus
    let avg1=avg2+1
    let for2=cpus
    let for1=nums-avg2*cpus
    for ((i=0;i<for1;i++));
    do
        eval $2 $i $cpus $avg1 &
    done
    for ((i;i<for2;i++));
    do
        eval $2 $i $cpus $avg2 &
    done
    wait
}

#获取kabi
function get_symvers()
{
    echo "--- 获取内核KABI ---"
    rm -rf $KABI_TMP_DIR
    mkdir -p $KABI_TMP_DIR
    pre_c_s
    #并行处理所有的文件
    thread_alloc $kabi_files_num build_cs
    echo "--- 文件处理完成 ---"
    #将查到的kabi汇总到KABI_SYMS
    KABI_LIST=(`cat $KABI_TMP_DIR/* | awk '{gsub(/;|^__crc_/,"");printf("%s\t%s\n",$3,$1)}' | tee $KABI_SYMS`)
    echo "--- 获取内核KABI完成 ---"
}

function kabi_check()
{
    echo "--- 开始校验 ---"
    MODULE_KABI_LIST=(`awk '{printf("%s\t%s\n",$1,$2)}' $Module_kabi_f | tee $MODULE_TMP_KABI`)
    ERROR_KABI=$(grep -vf $KABI_SYMS $MODULE_TMP_KABI && grep -vf $MODULE_TMP_KABI $KABI_SYMS >> /dev/null)
    if (($?==0)); then
        echo "--- 校验失败，错误KABI如下:"
        echo "$ERROR_KABI"
        return 1
    fi
    echo "--- 校验完成，没有问题 ---"
    return 0
}

#需要初始化内核环境，包括必要的工具和源码
function init_kernel()
{
    if [ "$ARCH" = "x86_64" ]; then
        config_arch="x86"
    elif [ "$ARCH" = "aarch64" ]; then
        config_arch="arm64"
    else 
        echo "暂时不支持 "${ARCH}" 架构"
        return 1
    fi

    KERNEL_CONFIG_PATH="arch/${config_arch}/configs/${kernel_config}"
    if [ ! -f $KERNEL_CONFIG_PATH ]; then
        echo "未找到配置文件："${KERNEL_CONFIG_PATH}
        return 2
    fi

    echo "--- 初始化环境 ---"
    make distclean -j ${cpus}
    (cp $KERNEL_CONFIG_PATH .config && \
    make olddefconfig -j ${cpus} && \
    make scripts -j ${cpus} && \
    make prepare -j ${cpus} && \
    make init -j ${cpus} ) >> /dev/null 2>&1
    make drivers/scsi/scsi_sysfs.o
    echo "--- 环境初始化完成 ---"
}

function clean_all()
{
    if [ -z "$kabi_files_S_sc" ] && [ -n "$Kabi_path_f" ]; then
        kabi_files_S_sc=`grep "\.S" $Kabi_path_f | sed 's/\.S$/\.s\.c/'`
    fi
    rm -rf $KABI_TMP_DIR $KABI_SYMS $kabi_files_S_sc
    make distclean -j >> /dev/null
}

#处理脚本参数
function fargs()
{
    if (( $help_flag > 0 )); then
        help_info
        exit 0
    fi
    if (( $init_flag > 0 )) && [ -n kernel_config ]; then 
        init_kernel
        retnum=$?
    fi
    if [ -n "$Module_kabi_f" ]; then
        if (($fp_flags > 0)); then
            findpath $Module_kabi_f Kabi.path_$ARCH
        elif [ -n "$Kabi_path_f" ]; then
            get_symvers
            kabi_check
            retnum=$?
        fi 
    fi
    if (( $clean_flag > 0 )); then
        clean_all
    fi
    exit $retnum
}

fargs